import java.util.ArrayList;
import java.util.Arrays;

import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import java.io.*;

import static java.lang.System.err;
import static java.lang.System.out;

public abstract class ComputerPlayer extends Player {
    protected static final int BLACK_WIN  = -Integer.MAX_VALUE+1000;
    protected static final int BLACK_LOSS =  Integer.MAX_VALUE-1000;
    protected static final int WHITE_WIN  =  Integer.MAX_VALUE-1000;
    protected static final int WHITE_LOSS = -Integer.MAX_VALUE+1000;

    static final int MAX = Integer.MAX_VALUE;
    static final int MIN = -MAX;

    protected static final int DRAW = 100000000;

    protected Eval evaluator;
    protected int searchDepth;
    protected boolean printBestMoveValue;

    int nbrNodesVisited;
    int[] nbrMovesCount;
    int[] depthCount;

    protected HashMap<Board, Board> openings = new HashMap<Board, Board>();
    private static final String opBook = "opBook.cwo";

    public ComputerPlayer(boolean isBlack, Eval evaluator, int searchDepth) {
        super(isBlack);
        this.evaluator = evaluator;
        this.searchDepth = searchDepth;
        fillMap(openings);
        printBestMoveValue = false;
        nbrNodesVisited = 0;
        // 200 is just some exagerated upper limit
        nbrMovesCount = new int[200];
        Arrays.fill(nbrMovesCount, 0);
        depthCount = new int[200];
        Arrays.fill(depthCount, 0);
    }

    public void setPrintBestMoveValue(boolean printIt) {
        printBestMoveValue = printIt;
    }

    private void fillMap(HashMap<Board, Board> map) {
        try{
            BufferedReader input = new BufferedReader(new FileReader(opBook));
            try{
                String line = null;
                Pattern p = Pattern.compile("\\<(.*)\\>\\s*\\<(.*)\\>\\s*");
                Matcher m = null;
                String board = "";
                String move = "";
                Board key = null;
                Board value = null;
                while((line = input.readLine()) != null){
                    m = p.matcher(line);
                    if(m.matches()){
                        board = m.group(1);
                        move = m.group(2);
                    }
                    key = Board.parseWdp(board);
                    value = new Board(key);
                    value.makeMove(HumanPlayer.parseMove(move));
                    map.put(key,value);
                }
            }
            finally{
                input.close();
            }
        }
        catch (IOException e){
            System.out.println("ComputerPlayer: Failed to load opening book!");
        }
    }

    /*
    public abstract Board findMove(Board board);

    @Override
    public Action getMove(Board board, int moveNbr) {
        long startTime = System.currentTimeMillis();

        assert board.blackToMove == isBlack;
        Board bestMove = findMove(board);

        int[] myMove;
        if (bestMove != null) {
            myMove = movePathFromBoards(board, bestMove);
            out.print("My move is " + moveNbr + ": " + myMove[0]);
            for (int i = 1; i < myMove.length; i++) {
                out.print(" - " + myMove[i]);
            }
            out.println();
        } else {
            out.println(color + " couldn't find any available moves.");
            myMove = new int[0];
        }

        long endTime = System.currentTimeMillis();
        long thisMove = endTime - startTime;
        spentTime += thisMove;
        float thisMoveTime = thisMove / 1000.0F;
        float totalTime = spentTime / 1000.0F;
        out.printf("This move took: %.2f s.\n", thisMoveTime);
        out.printf("Total spent time: %.2f s.\n", totalTime);

        return new Action(myMove, spentTime);
    }
    */


    public static void fromTo(Board currentBoard, Board nextBoard,
                              int[] fromAndTo)
    {
        int currentMovingColor;
        int currentOtherColor;
        int nextMovingColor;
        int nextOtherColor;
        if (currentBoard.blackToMove) {
            currentMovingColor = currentBoard.black;
            currentOtherColor  = currentBoard.white;
            nextMovingColor = nextBoard.black;
            nextOtherColor  = nextBoard.white;
        } else {
            currentMovingColor = currentBoard.white;
            currentOtherColor  = currentBoard.black;
            nextMovingColor = nextBoard.white;
            nextOtherColor  = nextBoard.black;
        }

        int currentEmpty = ~(currentMovingColor | currentOtherColor);
        int nextEmpty = ~(nextMovingColor | nextOtherColor);

        int fromTo = currentMovingColor ^ nextMovingColor;

        int capturedPieces = currentOtherColor ^ nextOtherColor;
        int nbrCapturedPieces = BitUtils.countBits(capturedPieces);

        if (fromTo != 0) {
            int fromBit = fromTo & nextEmpty;
            int toBit   = fromTo & currentEmpty;
            int fromIndex = BitUtils.floorLog2(fromBit);
            int toIndex   = BitUtils.floorLog2(toBit);
            fromAndTo[0] = fromIndex;
            fromAndTo[1] = toIndex;
        } else {
            loopFromTo(currentMovingColor, capturedPieces, nbrCapturedPieces,
                       fromAndTo);
        }
    }



    private static final int[] capturedMask = {
        0x00000030, 0x00000060, 0x000000c0, 0x00000080,
        0x00000101, 0x00000303, 0x00000606, 0x00000c0c,
        0x00003030, 0x00006060, 0x0000c0c0, 0x00008080,
        0x00010100, 0x00030300, 0x00060600, 0x000c0c00,
        0x00303000, 0x00606000, 0x00c0c000, 0x00808000,
        0x01010000, 0x03030000, 0x06060000, 0x0c0c0000,
        0x30300000, 0x60600000, 0xc0c00000, 0x80800000,
        0x01000000, 0x03000000, 0x06000000, 0x0c000000,
    };

    public static int[] movePathFromBoards(Board currentBoard, Board nextBoard)
    {
        int currentMovingColor;
        int currentOtherColor;
        int nextMovingColor;
        int nextOtherColor;
        if (currentBoard.blackToMove) {
            currentMovingColor = currentBoard.black;
            currentOtherColor  = currentBoard.white;
            nextMovingColor = nextBoard.black;
            nextOtherColor  = nextBoard.white;
        } else {
            currentMovingColor = currentBoard.white;
            currentOtherColor  = currentBoard.black;
            nextMovingColor = nextBoard.white;
            nextOtherColor  = nextBoard.black;
        }

        int currentEmpty = ~(currentMovingColor | currentOtherColor);
        int nextEmpty = ~(nextMovingColor | nextOtherColor);

        int fromTo = currentMovingColor ^ nextMovingColor;

        int capturedPieces = currentOtherColor ^ nextOtherColor;
        int nbrCapturedPieces = BitUtils.countBits(capturedPieces);

        if (fromTo == 0) {
            return loopPath(currentMovingColor, capturedPieces,
                            nbrCapturedPieces);
        }

        int fromBit = fromTo & nextEmpty;
        int toBit   = fromTo & currentEmpty;
        int fromIndex = BitUtils.floorLog2(fromBit);
        int toIndex   = BitUtils.floorLog2(toBit);

        int[] move;
        if (nbrCapturedPieces == 0) {
            move = new int[2];
        } else {
            move = new int[nbrCapturedPieces + 1];
        }
        move[0] = fromIndex + 1;
        move[move.length - 1] = toIndex + 1;

        int capturedPiecesLeft = nbrCapturedPieces;
        int jumpingFromIndex = fromIndex;
        int moveIndex = 1;
        while  (capturedPiecesLeft > 0) {
            int capturedNeighbors =
                capturedPieces & capturedMask[jumpingFromIndex];

            if ((capturedNeighbors & (capturedNeighbors - 1)) != 0) {
                // There is a loop and we can't handle that right now
                String error = "There are more than 1 captured piece near the"
                            + " moving piece and we can't handle that yet.";
                err.println(error);
                throw new UnsupportedOperationException(error);
            }
            assert BitUtils.countBits(capturedNeighbors) == 1;

            capturedPieces ^= capturedNeighbors;
            int capturedIndex = BitUtils.floorLog2(capturedNeighbors);

            // 0 for even rows, 1 for odd rows
            int rowModifier = (fromIndex >>> 2) & 1;
            int capturedDistance = capturedIndex - jumpingFromIndex
                + rowModifier;

            int jumpingTo = Integer.MIN_VALUE;
            switch (capturedDistance) {
            case -4:
                // Capturing north west
                jumpingTo = jumpingFromIndex - 9;
                break;
            case -3:
                // Capturing north east
                jumpingTo = jumpingFromIndex - 7;
                break;
            case 4:
                // Capturing south west
                jumpingTo = jumpingFromIndex + 7;
                break;
            case 5:
                // Capturing south east
                jumpingTo = jumpingFromIndex + 9;
                break;
            default:
                //Error
                String error = "Unknown capturing distance " + capturedDistance
                    + ". This means either that the move generation or the"
                    + " movePathFromBoards() is faulty.";
                err.println(error);
                throw new RuntimeException(error);
            }

            capturedPiecesLeft--;
            move[moveIndex] = jumpingTo + 1;
            moveIndex++;
            jumpingFromIndex = jumpingTo;
        }
        assert (nbrCapturedPieces == 0) || (jumpingFromIndex == toIndex);
        assert (nbrCapturedPieces == 0) || (moveIndex == move.length);

        return move;
    }



    /**
     * NB! This method should only be called when the move is a loop, such that
     * the starting square and the end square are the same. This should only be
     * possible when you capture 4 or 6 pieces.
     *
     * nbrCapturedPieces MUST be equal to 4 or 6.
     */
    public static void loopFromTo(int movingColor, int capturedPieces,
                                  int nbrCapturedPieces, int[] fromTo)
    {
        int[] jumpingLoops;
        int[] capturedLoops;
        int[][] pathLoops;
        if (nbrCapturedPieces == 4) {
            jumpingLoops = JUMPING_LOOPS4;
            capturedLoops = CAPTURED_LOOPS4;
            pathLoops = PATH_LOOPS4;
        } else if (nbrCapturedPieces == 6) {
            jumpingLoops = JUMPING_LOOPS6;
            capturedLoops = CAPTURED_LOOPS6;
            pathLoops = PATH_LOOPS6;
        } else {
            // Error
            throw new RuntimeException();
        }

        for (int i = 0; i < jumpingLoops.length; i++) {
            if ((capturedPieces & capturedLoops[i]) == capturedLoops[i]) {
                int jumper = movingColor & jumpingLoops[i];

                assert jumper != 0;
                assert BitUtils.countBits(jumper) == 1;

                int startEndIndex = BitUtils.floorLog2(jumper);
                fromTo[0] = startEndIndex;
                fromTo[1] = startEndIndex;

                break;
            }
        }
    }

    /* All possible patterns for squares where a piece is jumping in a loop of
     * length 4.
     * Indices are synced with indices in CAPTURED_LOOPS4 and PATH_LOOPS4.
     */
    public static final int[] JUMPING_LOOPS4 = {
        0x20502,   0x40A04,   0x205020,   0x40A040,
        0x2050200, 0x40A0400, 0x20502000, 0x40A04000,
    };

    /* All possible patterns for squares where 4 pieces are captured in a loop.
     * Indices are synced with indices in CAPTURED_LOOPS4 and PATH_LOOPS4.
     */
    public static final int[] CAPTURED_LOOPS4 = {
        0x6060,   0xC0C0,   0x30300,   0x60600,
        0x606000, 0xC0C000, 0x3030000, 0x6060000,
    };

    /* These squares are indexed from 1 (not from 0).
     * Indices are synced with indices in CAPTURED_LOOPS4 and PATH_LOOPS4.
     */
    public static final int[][] PATH_LOOPS4 = {
        { 2,  9, 18, 11},
        { 3, 10, 19, 12},
        { 6, 13, 22, 15},
        { 7, 14, 23, 16},
        {10, 17, 26, 19},
        {11, 18, 27, 20},
        {14, 21, 30, 23},
        {15, 22, 31, 24},
    };

    /* All possible patterns for squares where a piece is jumping in a loop of
     * length 6.
     * Indices are synced with indices in CAPTURED_LOOPS6 and PATH_LOOPS6.
     */
    public static final int[] JUMPING_LOOPS6 = {
        0x40A0502, 0x40A05020, 0x2050A04, 0x2050A040,
    };

    /* All possible patterns for squares where 6 pieces are captured in a loop.
     * Indices are synced with indices in CAPTURED_LOOPS6 and PATH_LOOPS6.
     */
    public static final int[] CAPTURED_LOOPS6 = {
        0xC0A060, 0x6050300, 0x60A0C0, 0x3050600,
    };

    /* These squares are indexed from 1 (not from 0).
     * Indices are synced with indices in CAPTURED_LOOPS4 and PATH_LOOPS4.
     */
    public static final int[][] PATH_LOOPS6 = {
        {2,  9, 18, 27, 20, 11},
        {6, 13, 22, 31, 24, 15},
        {3, 10, 17, 26, 19, 12},
        {7, 14, 21, 30, 23, 16},
    };

    /**
     * NB! This method should only be called when the move is a loop, such that
     * the starting square and the end square are the same. This should only be
     * possible when you capture 4 or 6 pieces.
     *
     * nbrCapturedPieces MUST be equal to 4 or 6.
     */
    public static int[] loopPath(int currentMovingColor, int capturedPieces,
                                 int nbrCapturedPieces)
    {
        int[] jumpingLoops;
        int[] capturedLoops;
        int[][] pathLoops;
        if (nbrCapturedPieces == 4) {
            jumpingLoops = JUMPING_LOOPS4;
            capturedLoops = CAPTURED_LOOPS4;
            pathLoops = PATH_LOOPS4;
        } else if (nbrCapturedPieces == 6) {
            jumpingLoops = JUMPING_LOOPS6;
            capturedLoops = CAPTURED_LOOPS6;
            pathLoops = PATH_LOOPS6;
        } else {
            // Error
            throw new RuntimeException();
        }

        int[] move = new int[nbrCapturedPieces + 1];

        for (int i = 0; i < jumpingLoops.length; i++) {
            if ((capturedPieces & capturedLoops[i]) == capturedLoops[i]) {
                int jumper = currentMovingColor & jumpingLoops[i];

                assert jumper != 0;
                assert BitUtils.countBits(jumper) == 1;

                int startEndSquare = BitUtils.floorLog2(jumper) + 1;

                int[] pathSquares = pathLoops[i];
                int j = 0;
                while ((j < nbrCapturedPieces)
                       && (pathSquares[j] != startEndSquare))
                {
                    // Just find the first matching index
                    j++;
                }

                assert j < nbrCapturedPieces;
                assert pathSquares[j] == startEndSquare;

                for (int k = 0; k < nbrCapturedPieces; k++) {
                    move[k] = pathSquares[j];
                    j++;
                    if (j == nbrCapturedPieces) {
                        j = 0;
                    }
                }
                move[move.length - 1] = startEndSquare;

                break;
            }
        }

        return move;
    }

    public static void printStats(int[] counts) {
        int maxNonZeroIndex = counts.length - 1;
        while ((maxNonZeroIndex > 0) && (counts[maxNonZeroIndex] == 0)) {
            maxNonZeroIndex--;
        }

        int[] cumulativeSum = new int[maxNonZeroIndex + 1];
        cumulativeSum[0] = counts[0];
        for (int i = 1; i <= maxNonZeroIndex; i++) {
            cumulativeSum[i] = cumulativeSum[i - 1] + counts[i];
        }

        int total = cumulativeSum[maxNonZeroIndex];

        out.printf("%2s %8s %5s %8s %6s\n", "Value", "Count", "%",
                   "Cumul.", "%");
        int avgSum = 0;
        for (int i = 0; i <= maxNonZeroIndex; i++) {
            float countPercent = (100.0F * counts[i]) / total;
            float cumulPercent = (100.0F * cumulativeSum[i]) / total;
            out.printf("%5d %8d %5.2f %8d %6.2f\n", i, counts[i], countPercent,
                       cumulativeSum[i], cumulPercent);
            avgSum += i * counts[i];
        }

        int medianCount = cumulativeSum[maxNonZeroIndex] / 2;
        int median = 0;
        while (cumulativeSum[median] < medianCount) {
            median++;
        }
        out.println("Median = " + median);

        if (cumulativeSum[maxNonZeroIndex] > 0) {
            double avg = ((double) avgSum) / cumulativeSum[maxNonZeroIndex];
            out.printf("Average = %f\n", avg);
        }
    }
}
